﻿namespace CommonLibrary.WorkFlow
{
    using CommonLibrary.Models;
    using CommonLibrary.WorkFlow.Enums;
    using CommonLibrary.WorkFlow.Exceptions;
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Linq;

    public class ApprovalFlow : IWorkFlow
    {
        private WorkFlowManager manager;
        private DataRow flowObject = null;
        private DataRow task = null;
        private Step step = null;

        public ApprovalFlow(WorkFlowManager manager)
        {
            this.manager = manager;
        }

        public WorkResult AddTaskObject(Step step, DataRow task, DataRow flowObject)
        {
            this.flowObject = flowObject;
            this.task = task;
            this.step = step;

            switch ((ActionType)task["actiontype"])
            {
                case ActionType.Single:
                    SingleMode();
                    break;

                case ActionType.Multi:
                    MultiMode();
                    break;

                case ActionType.Role:
                    RoleMode();
                    break;

                case ActionType.ParentUser:
                    ParentUserMode();
                    break;

                case ActionType.Position:
                    PositionMode();
                    break;

                default:
                    throw new ArgumentException("ActionType");
            }

            return WorkResult.Continue;
        }

        public WorkResult Execute(
            DataRow taskObject,
            Dictionary<string, object> values)
        {
            var state = (ApprovalType)(int)values["state"];
            switch (state)
            {
                case ApprovalType.Approve:
                    OnApprove(
                        (int)taskObject["flowobjectid"],
                        (int)taskObject["id"],
                        (int)taskObject["taskid"],
                        values);
                    break;

                case ApprovalType.Deny:
                    OnDeny(
                        (int)taskObject["flowobjectid"],
                        (int)taskObject["id"],
                        (int)taskObject["taskid"],
                        values);
                    return WorkResult.Break;

                case ApprovalType.Move:
                    OnMove(taskObject, values);
                    return WorkResult.Break;
            }

            return WorkResult.Continue;
        }

        #region 流转
        private void SingleMode()
        {
            int userId = (int)task["touser"];

            // 用户不存在 或者 审批人和申请人相同
            if (manager.Database.sys_User.Where(k => k.Id == userId && k.State == 1).Count() == 0 ||
                userId == (int)flowObject["createuserid"])
            {
                throw new WorkUserNotFoundException();
            }

            // 添加task object
            Insert(userId);

            // 发送通知邮件
            this.SendProgressEmail(userId, (int)flowObject["id"]);
        }

        private void MultiMode()
        {
            var users = (int[])task["touserlist"];
            if (users.Contains((int)flowObject["createuserid"]))
                throw new WorkUserNotFoundException();

            foreach (var user in users)
            {
                // 添加task object
                Insert(user);

                // 发送通知邮件
                this.SendProgressEmail(user, (int)flowObject["id"]);
            }
        }

        private void RoleMode()
        {
            var role = (int)task["torole"];
            var indept = (bool)task["indept"];
            var users = indept ?
                DbHelper.QueryUsersByOrgRole(manager.Database, (int)flowObject["createdeptid"], role) :
                DbHelper.QueryUsersByRole(manager.Database, role);

            if (users.Contains((int)flowObject["createuserid"]))
                throw new WorkUserNotFoundException();

            foreach (var user in users)
            {
                // 添加task object
                Insert(user);

                // 发送通知邮件
                this.SendProgressEmail(user, (int)flowObject["id"]);
            }
        }

        private void ParentUserMode()
        {
            var parentUser = DbHelper.GetParentUser(
                manager.Database,
                (int)flowObject["createuserid"],
                (int)flowObject["createdeptid"]);

            if (parentUser == null)
                throw new WorkUserNotFoundException();

            // 添加task object
            Insert(parentUser.Value);

            // 发送通知邮件
            this.SendProgressEmail(parentUser.Value, (int)flowObject["id"]);
        }

        private void PositionMode()
        {
            // TODO:
        }

        private void Insert(int user)
        {
            DbHelper.InsertTaskObject(
                manager.Database,
                (int)task["id"],
                (int)flowObject["id"],
                user,
                step.StepId,
                step.Name.ToString());
        }
        #endregion

        #region 审批
        private void OnApprove(
            int flowObjectId,
            int taskObjectId,
            int taskId,
            Dictionary<string, object> values)
        {
            var result = (string)values["result"];

            // 通过
            UpdateTaskObject(taskObjectId, TaskObjectState.Adopt, result);

            // 跳过其他节点
            this.SkipOtherTaskObject(flowObjectId, taskId);
        }

        private void OnDeny(
            int flowObjectId,
            int taskObjectId,
            int taskId,
            Dictionary<string, object> values)
        {
            var result = (string)values["result"];

            // 拒绝
            UpdateTaskObject(taskObjectId, TaskObjectState.Deny, result);

            // 跳过其他节点
            this.SkipOtherTaskObject(flowObjectId, taskId);

            // 结束-未通过
            this.UpdateFlowObject(flowObjectId, FlowObjectState.Deny, null);

            // 通知审批被拒绝的消息
            this.SendFaultEmail(flowObjectId);
        }

        private void OnMove(
            DataRow taskObject,
            Dictionary<string, object> values)
        {
            var result = (string)values["result"];
            var userId = (int)values["userId"];

            var user = manager.Database.sys_UserInfo.Where(k => k.UserId == userId)
                              .Select(k => k.UserName).First();

            if ((int)taskObject["state"] != 1)
                return;

            // 修改当前节点状态
            if (string.IsNullOrWhiteSpace(result))
                result = "由[" + user + "]处理此申请";
            else
                result = "由[" + user + "]处理此申请: " + result.Trim();

            UpdateTaskObject((int)taskObject["id"], TaskObjectState.Appoint, result);

            // 检查下一个节点
            if (CheckTaskObject((int)taskObject["flowobjectid"], (int)taskObject["taskid"], userId))
            {
                // 添加下一个节点
                DbHelper.InsertTaskObject(
                            manager.Database,
                            (int)taskObject["taskid"],
                            (int)taskObject["flowobjectid"],
                            userId,
                            (int)taskObject["stepid"],
                            "指派审批");

                // 发送邮件通知
                this.SendProgressEmail(userId, (int)taskObject["flowobjectid"]);
            }
        }

        private void UpdateTaskObject(int taskObjectId, TaskObjectState state, string result)
        {
            DbHelper.UpdateTaskObject(manager.Database, taskObjectId, (int)state, result);
        }

        private void SkipOtherTaskObject(int flowObjectId, int taskId)
        {
            string cmd =
@"update wf_taskobject set state = @state, updatedtime = now()
where flowobjectid = @flowobjectid and taskid = @taskid and state = 1;";
            manager.Database.SetCommand(
                cmd,
                manager.Database.Parameter("@state", (int)TaskObjectState.Skip),
                manager.Database.Parameter("@flowobjectid", flowObjectId),
                manager.Database.Parameter("taskid", taskId))
                   .ExecuteNonQuery();
        }

        private void UpdateFlowObject(int flowObjectId, FlowObjectState state, string description)
        {
            DbHelper.UpdateFlowObject(manager.Database, flowObjectId, (int)state, description);
        }

        private bool CheckTaskObject(
            int flowobjectid,
            int taskid,
            int user)
        {
            string cmd =
@"select count(id) from wf_taskobject where 
    flowobjectid = @flowobjectid and
    taskid = @taskid and
    workuserid = @user and state = 1;";

            int count = manager.Database.SetCommand(
                                    cmd,
                                    manager.Database.Parameter("@flowobjectid", flowobjectid),
                                    manager.Database.Parameter("@taskid", taskid),
                                    manager.Database.Parameter("@user", user))
                                .ExecuteScalar<int>();
            return count == 0;
        }
        #endregion

        private void SendFaultEmail(int flowObjectId)
        {
            try
            {
                var flowObject = DbHelper.GetViewFlowObject(manager.Database, flowObjectId);
                MailManager.SendFaultEmail(
                    manager.Database,
                    (int)flowObject["createuserid"],
                    (string)flowObject["flowname"]);
            }
            catch
            {
            }
        }

        private void SendProgressEmail(int userid, int flowObjectId)
        {
            try
            {
                var flowObject = DbHelper.GetViewFlowObject(manager.Database, flowObjectId);
                MailManager.SendProgressEmail(
                    manager.Database,
                    userid,
                    (int)flowObject["createuserid"],
                    (string)flowObject["flowname"]);
            }
            catch
            {
            }
        }
    }
}
